iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0
生成式 AI

使用 Spring AI 打造企業 RAG 知識庫系列 第 15

使用 Spring AI 打造企業 RAG 知識庫【15】- ChatClient vs ChatModel

  • 分享至 

  • xImage
  •  

既生瑜,何生亮

https://ithelp.ithome.com.tw/upload/images/20240815/20161290XZLxuW3Mlf.png
記得我們 Day3 提到要自動綁定 ChatClient 卻失敗吧,今天來看看如何解決

▋程式碼實作

要初始化 ChatClient 只能使用 Builder 建立

@RestController
class MyController {
    private final ChatClient chatClient;
    public MyController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }
}

若有其他 Component 會使用 AI,可以在 Config 類別建立 Bean,要使用的 Component 在自動綁定即可

@Configuration
class Config {
    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.build();
    }
}

其實凱文大叔一直覺得之後的改版一定會讓 ChatClient 可以自動綁定

▋Client在Spring框架的意義

前面我們 ChatModel 也用得好好的,為何在講記憶之前要先說 ChatClient ?

在 Spring 框架中,結尾冠上 Client 的類別,就是更高級的封裝,例如:RestClient、WebClient、JdbcClient等,而且這一系列的類別都提供 Fluent 風格的 API,雖然 ChatModel 也提供一部分的 Fluent API,不過僅限於類別本身的參數,許多外部設定還是得先建立其他物件在引入

ChatClient 最大的差別就是將這些外部類別設定的資料也一起整合進來,下面就來比較 ChatModel 與 ChatClient 寫法的差異吧

▋ChatClient與ChatModel用法差異

1. Prompt

ChatModel:

ChatResponse response = chatModel
				.call(new Prompt(
			    	new UserMessage("Tell me a joke"),
			    ));

ChatClient:

ChatResponse chatResponse = chatClient.prompt()
    .user("Tell me a joke")
    .call()
    .chatResponse();

這個範例看起來雖然程式碼差不多,不過可以看到 ChatClient 在設定 Prompt 時直接使用 .prompt(),ChatModel 卻要自己 new 一個,這就是兩者最大的差別

2. PromptTemplate

ChatModel:

@GetMapping("/template")
	public String template1(@RequestParam String llm) {	
		String template = "請問{llm}目前有哪些模型,各有甚麼特殊能力";
		PromptTemplate promptTemplate = new PromptTemplate(template);
		Prompt prompt = promptTemplate.create(Map.of("llm", llm));
		ChatResponse response = chatModel.call(prompt);
		return response.getResult().getOutput().getContent();
	}

ChatClient:

	@GetMapping("/template")
	public String template(@RequestParam String llm) {
		String template = "請問{llm}目前有哪些模型,各有甚麼特殊能力";
		ChatResponse response = chatClient.prompt()
				.user(u -> u.text(template)
						.param("llm", llm)
						)
				.call()
				.chatResponse();
		return response.getResult().getOutput().getContent();
	}

這個例子可以看出 ChatClient 在設定 Message 時直接使用 Lambda 語法建立 PromptTemplate 並使用 param 帶入參數,一氣呵成

3. Structured Output Converter

ChatModel:

record ActorsFilms(String actor, List<String> movies) {};
@GetMapping("/films")
public ActorsFilms films(String actor) {
	String template = """
	        列出演員{actor}最有名的五部電影,需用繁體中文回答
	        {format}
	        """;
	BeanOutputConverter<ActorsFilms> beanOutputConverter =
		    new BeanOutputConverter<>(ActorsFilms.class);
	String format = beanOutputConverter.getFormat();
	Generation generation = chatModel.call(
		    new Prompt(new PromptTemplate(template, Map.of("actor", actor, "format", format)).createMessage())).getResult();
	ActorsFilms actorsFilms = beanOutputConverter.convert(generation.getOutput().getContent());
	return actorsFilms;
}

ChatClient:

record ActorsFilms(String actor, List<String> movies) {};
@GetMapping("/films")
public ActorsFilms films(String actor) {
String template = """
	        列出演員{actor}最有名的五部電影,需用繁體中文回答
	        """;
	ActorFilms actorFilms = chatClient.prompt()
    .user(u -> u.text(template).param("actor", actor))
    .call()
    .entity(ActorFilms.class);
  return actorFilms;
}

結構化輸出時 ChatClient 竟然只用 .entity() 就取代 BeanOutputConverter 越複雜的案例使用 ChatClient 就會讓程式碼更簡潔,ChatClient 其實是將 BeanOutputConverter 包進類別裡處理結構化輸出

4. Function Calling

因為 ChatClient 把 .prompt() 加入 Fluent API 中,原本要使用 Options 設定的 Function 的部分也跟著一起簡化了
ChatModel

@GetMapping("/func")
    public String func(String prompt) {
        return chatModel.call(
            new Prompt(prompt, 
               OpenAiChatOptions.builder()
               // Funciton可以放多筆,也能依據 API 接口放上合適的 Function
               .withFunction("ProductSalesInfo")
               .withFunction("ProductDetailsInfo")
               .build())
        		).getResult().getOutput().getContent();
    }

ChatClient

@GetMapping("/func")
    public String func(String prompt) {
        return chatClient.prompt().
            .user(prompt)
            .functions("ProductSalesInfo","ProductDetailsInfo")
            .call().content();
    }

Function 數量若不多,甚至能省略在 Config 中建立 Bean 的部分( 設定 Bean 的方式請參考 Day11),直接將 name、description 和 function 的實體一起放在 chatClient 中,如下面的程式碼

@GetMapping("/func")
    public String func(String prompt) {
        return chatClient.prompt().
            .user(prompt)
            .function("CurrectDateTime","Get the Date Time",new CurrectDateTimeFunction())
            .call().content();
    }

5. Using Defaults

在 ChatClient 中還能設定 Default 參數,這部分需要在 builder 時設定,之後執行時沒帶入參數就會使用預設的內容,舉個實際例子

@Configuration
class Config {
    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是個友善的聊天機器人,不管問甚麼問題都盡可能提供答案")
                .build();
    }
}

之後呼叫 chatClient 時若沒使用 .system() 覆蓋 SystemMessage,Spring AI 就會使用 defaultSystem 的內容來作為SystemMessage

下面是預設的方法以及正常呼叫的方法

Default Standard
defaultSystem system
defaultUser user
defaultFunction function
defaultFunctions functions
defaultOptions options
defaultAdvisors advisors

有注意到 advisors 嗎?這是前面沒提過的內容,也會是後面記憶跟 RAG 最重要的調用方法,就留在後面慢慢說明囉

▋回顧

今天學到的內容:

  1. 了解 Client 結尾的 Class 在 Spring 框架的含意
  2. ChatClient 與 ChatModel 用法比較
    • Prompt
    • PromptTemplate
    • Structured Output Converter
    • Function Calling
    • Using Defaults

今天以後程式碼的部分會盡量使用 ChatClient 來撰寫,有了前面幾天的基礎在看到簡化後的程式碼應該會更容易上手

▋Source Code

今日程式碼: https://github.com/kevintsai1202/SpringBoot-AI-Day15.git


▋認識凱文大叔

凱文大叔使用 Java 開發程式超過 20 年,對於 Java 生態非常熟悉,曾使用反射機制開發 ETL 框架,對 Spring 背後的原理非常清楚,目前以 Spring Boot 作為後端開發框架,前端使用 React 搭配 Ant Design
下班之餘在 Amazing Talker 擔任程式語言講師,並獲得學員的一致好評

最近剛成立一個粉絲專頁-凱文大叔教你寫程式 歡迎大家多追蹤,我會不定期分享實用的知識以及程式開發技巧

想討論 Spring 的 Java 開發人員可以加入 FB 討論區 Spring Boot Developer Taiwan

我是凱文大叔,歡迎一起加入學習程式的行列


上一篇
使用 Spring AI 打造企業 RAG 知識庫【14】- 結構化資料轉換器
下一篇
使用 Spring AI 打造企業 RAG 知識庫【16】- 魔鏡~誰是Spring AI的專家?
系列文
使用 Spring AI 打造企業 RAG 知識庫35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言